﻿////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// <copyright>Copyright 2008-2011 Andreas Huber Doenni</copyright>
// Distributed under the Microsoft Public License (Ms-PL).
// See accompanying file License.txt or copy at http://mews.codeplex.com/license.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace Mews.Mime
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Drawing;
    using System.Drawing.Imaging;
    using System.IO;

    /// <summary>Represents an entity body (see <a href="http://www.ietf.org/rfc/rfc2045.txt">RFC 2045</a> 2.4. &amp;
    /// 2.6.) with image content (see <a href="http://www.ietf.org/rfc/rfc2046.txt">RFC 2046</a> 4.2.).</summary>
    /// <threadsafety static="true" instance="false"/>
    public sealed class ImageBody : Body<ImageBody>
    {
        private static readonly byte[] defaultContent = GetDefaultContent();
        private byte[] content = defaultContent;

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>Initializes a new instance of the <see cref="ImageBody"/> class.</summary>
        /// <param name="subtype">The subtype of the image body, usually one of the <see cref="ImageSubtypeNames"/>
        /// members.</param>
        /// <exception cref="ArgumentException"><paramref name="subtype"/> is <c>null</c> or empty.</exception>
        public ImageBody(string subtype) : base(subtype)
        {
        }

        /// <summary>Gets a copy of the body content.</summary>
        /// <remarks>
        /// <para>By calling this method, the returned <see cref="Bitmap"/> object is put into the possession of the
        /// calling method, which is responsible for properly disposing it.</para>
        /// <para>The default value is a bitmap with 1 pixel width and height, which is set during construction.</para>
        /// </remarks>
        [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "A call to this function possibly uses a lot of resources.")]
        public Bitmap GetContent()
        {
            return MakeBitmap(this.content);
        }

        /// <summary>Sets the body content.</summary>
        /// <exception cref="ArgumentException">The format of the image is unrecognized.</exception>
        public void SetContent(MemoryStream value)
        {
            byte[] bytes = value.ToArray();

            // The following line ensures that the stream contents can actually be interpreted as a bitmap. This is a
            // bit unfortunate, as we're doing the same (possibly resource-heavy) work twice. However, there's no easy
            // way how we could avoid this. We can't store the bitmap as a member of this class because we'd then need
            // to have it implement IDisposable. This in turn would force us to make all other classes that can
            // directly or indirectly own an object of this class disposable as well...
            MakeBitmap(bytes).Dispose();
            this.content = bytes;
        }

        /// <summary>Gets a copy of the bytes that make up the image.</summary>
        public byte[] GetContentBytes()
        {
            byte[] result = new byte[this.content.Length];
            Buffer.BlockCopy(this.content, 0, result, 0, result.Length);
            return result;
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        /// <summary>See <see cref="Body.WriteTo"/>.</summary>
        protected internal sealed override void WriteTo(
            Stream stream, Action<IEnumerable<HeaderField>> writeHeaderFieldsCallback)
        {
            writeHeaderFieldsCallback(this.GetRequiredHeaderFields(
                MediaTypeNames.Image, new KeyValuePair<string, string>[0], ContentTransferEncodingNames.Base64));
            Mews.IO.StreamHelper.WriteLineBreak(stream);

            using (var base64Stream = new Mews.IO.Base64Stream(new Mews.IO.ForwardingStream(stream)))
            {
                base64Stream.Write(this.content, 0, this.content.Length);
            }
        }

        ////////////////////////////////////////////////////////////////////////////////////////////////////////////////

        private static Bitmap MakeBitmap(byte[] bytes)
        {
            try
            {
                return new Bitmap(new MemoryStream(bytes));
            }
            catch (Exception ex)
            {
                if (ex.GetType() == typeof(Exception))
                {
                    // On the CF 3.5, the method Microsoft.AGL.Common.MISC.HandleAR fails to handle PAL_ERROR.Failed,
                    // which leads to a stock System.Exception being thrown, see
                    // https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=433744
                    throw new ArgumentException("Bitmap load failed");
                }
                else
                {
                    throw;
                }
            }
        }

        private static byte[] GetDefaultContent()
        {
            using (var bitmap = new Bitmap(1, 1, PixelFormat.Format32bppRgb))
            using (var stream = new MemoryStream())
            {
                bitmap.Save(stream, ImageFormat.Bmp);
                return stream.ToArray();
            }
        }
    }
}
